import { ConfigureService } from '@app/configure';
import { Device } from '@app/db/entities/Device';
import { User } from '@app/db/entities/User';
import { UserWalletLog } from '@app/db/entities/UserWalletLog';
import { Injectable } from '@nestjs/common';
import { AliIotService } from 'apps/ali-iot/src/ali-iot.service';
import { DeviceService } from '../device/device.service';
import { ChargeOrderService } from '../charge-order/charge-order.service';
import { ChargeOrder } from '@app/db/entities/ChargeOrder';
import { CacheService } from '@app/cache';
import { PrivateChargeRecord } from '@app/db/entities/PrivateChargeRecord';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ChargingStation } from '@app/db/entities/ChargingStation';
import { WechatService } from '../wechat/wechat.service';
import * as dayjs from 'dayjs';
import { ChargingCard } from '@app/db/entities/ChargingCard';
import { UserService } from '../user/user.service';
import { ChargingCardLog } from '@app/db/entities/ChargingCardLog';

@Injectable()
export class ChargeService {
    public startChargeResKey:string; //开始充电结果的Key
    public endChargeResKey:string; //结束充电结果的Key

    public PstartChargeResKey:string; //开始充电结果的Key
    public PendChargeResKey:string; //结束充电结果的Key

    
    constructor(
        @InjectRepository(PrivateChargeRecord) public privateChargeRecord:Repository<PrivateChargeRecord>,
        @InjectRepository(ChargingStation) public chargingStation:Repository<ChargingStation>,
        @InjectRepository(ChargingCard) public chargingCard:Repository<ChargingCard>,
        private readonly aliIot:AliIotService,
        private readonly device:DeviceService,
        private readonly user:UserService,
        private readonly chargOrder:ChargeOrderService,
        private readonly config:ConfigureService,
        private readonly cache:CacheService,
        private readonly wechat:WechatService

    ){
        this.startChargeResKey = this.config.get('startChargeResKey')
        this.endChargeResKey = this.config.get('endChargeResKey')

        this.PstartChargeResKey = this.config.get('PstartChargeResKey')
        this.PendChargeResKey = this.config.get('PendChargeResKey')
    }

    async charge(userId:number, device:Device, deviceId:number, port:number, mode:number, chargeNumber:number, payType:number, isWx:boolean, openid:string, clientSource:number) {
        let amount = 0 //要支付的金额
        let unitPrice = 0 //电价单价
        let unitService = 0 //服务费单价
        let kwh = 0 //充电度数
        let times = 0 //充电时间

        const station = await this.chargingStation.findOne({
            where : {id : device.stationId},
            select : ['fastPricing', 'slowPricing']
        })

        if(device.chargeType == 1) {

        }else { //慢充
            if(station.slowPricing == 0) return null

            let pricing = await this.device.slowPricing(station.slowPricing)
            if(!pricing) {
                return null
            }

            unitPrice = Number(pricing.price)
            unitService = Number(pricing.service)

            if(mode == 0) { //时间
                kwh = chargeNumber * pricing.hourKwh
                times = chargeNumber * 60
                
            }else if(mode == 1){
                kwh = chargeNumber
            }

            amount = Number(((unitPrice + unitService) * kwh).toFixed(2)) //充电总价钱

            
        }

        let date = new Date()

        const orderNo = this.chargOrder.generateOrderNo(userId)

        let order:KeyToValueType<ChargeOrder> = {
            orderNo,
            payType : payType,
            clientSource,
            createTime : Math.floor(date.getTime() / 1000),
            unitPrice :  String(unitPrice),
            unitService : String(unitService),
            status : 0,
            deviceId,
            userId,
            port : port,
            feeType : mode,
            precharge : chargeNumber,
            chargeAmount : String(amount),
            feeAmount : '0',
            feeRewardAmount : '0'
        }

        if(device.owners != null && device.owners.length > 0) {
            order.merchantId = device.owners[0]
            const merchant = await this.chargOrder.queryMerchant(order.merchantId, ['profitRate'])
            order.profitRate = merchant.profitRate

            if(device.owners.length > 1) {
                order.subMerchantId = device.owners[1]
            }
        }

        if(device.stationId > 0) {
            order.stationId = device.stationId
        }


        const queryRunner = await this.chargOrder.createQueryRunner()
        await queryRunner.connect();
        await queryRunner.startTransaction();

        try {
            let orderId = 0


            if(payType == 1) { //后付费
                //加入订单
                const r1 = await queryRunner.manager.insert<ChargeOrder>(ChargeOrder, order)
                if(!r1.raw || r1.raw.affectedRows < 1) {
                    await queryRunner.rollbackTransaction();
                    return -1
                }

                orderId = r1.raw.insertId
                const out_order_no = String(orderId)

                if(isWx) { //微信的
                    const r = await this.wechat.payscoreServiceorder(out_order_no, openid, amount, dayjs(date).format('YYYYMMDDHHmmss'), '充电费用', 'chargeOrder/payscore/notify/wx/' + orderId)
                    if(r == null) {
                        await queryRunner.rollbackTransaction();
                        return -2
                    }
                }


                const res = await this.pubCharge(orderId, deviceId, port, mode, times, kwh, device.aliProduct)
                if(!res || !res.body || !res.body.success) {
                    this.wechat.payscoreServiceorderCancel(out_order_no, '充电失败')
                    await queryRunner.rollbackTransaction();
                    return -5
                }


                this.cache.set(this.startChargeResKey + orderId, 'w', 20)


                await queryRunner.commitTransaction();

                return orderId


            }else { //预付费
                order.payStatus = 1
                let user = await queryRunner.manager.findOne<User>(User, {
                    where : {id : userId},
                    select : ['amount', 'rewardAmount'],
                    lock : {
                        mode : 'pessimistic_read'
                    }
                })

                let rechargeBalance = Number(user.amount) //充值的余额
                let rewardBalance = Number(user.rewardAmount) //赠送的余额

                if((rechargeBalance + rewardBalance) < amount) { //余额不足
                    await queryRunner.rollbackTransaction();
                    return -1
                }

                let { walletLog, walletUpdate} = this.walletUpdate(amount, rechargeBalance, rewardBalance, order, user)
                walletLog.type = 2

                //更新钱包
                const r1 = await queryRunner.manager.update<User>(User, { id : userId }, walletUpdate)
                if(r1.affected < 1) {
                    await queryRunner.rollbackTransaction();
                    return -2
                }

                

                //加入订单
                const r2 = await queryRunner.manager.insert<ChargeOrder>(ChargeOrder, order)
                if(!r2.raw || r2.raw.affectedRows < 1) {
                    await queryRunner.rollbackTransaction();
                    return -3
                }
                walletLog.relationId = orderId = r2.raw.insertId

                //加入钱包明细
                walletLog.createTime = order.createTime
                const { raw } = await queryRunner.manager.insert<UserWalletLog>(UserWalletLog, walletLog)
                if(!raw || raw.affectedRows < 1) {
                    await queryRunner.rollbackTransaction();
                    return -4
                }

                //this.cache.set(this.startChargeResKey + orderId, 'w', 20)
                const res = await this.pubCharge(orderId, deviceId, port, mode, times, kwh, device.aliProduct)
                if(!res || !res.body || !res.body.success) {
                    await queryRunner.rollbackTransaction();
                    return -5
                }

            }


            this.cache.set(this.startChargeResKey + orderId, 'w', 20)


            await queryRunner.commitTransaction();

            return orderId

        } catch (error) {
            console.log(error)
            await queryRunner.rollbackTransaction();
            return null
        } finally {
            await queryRunner.release();
        }

        

    }

    async chargeByCard(deviceId:number, port:number, cardNo:string|number, mode:number) {
        const resMsg:any = {
            type : 16,
            event : 2,
            port,
            cardID : cardNo,
            //order : '0'.padStart(16, '0'),
            //msgId : '0'.padStart(32, '0'),
            s : 0
        }

        const lockKey = 'card:c:' + cardNo
        const lock = await this.cache.set(lockKey, '1', 10, 'NX')
        if(!lock) return null

        const device = await this.device.findOne({id : deviceId}, ['chargeType', 'stationId', 'owners', 'aliProduct'])

        let chargeNumber = 0 //可以充多少

        let amount = 0 //要支付的金额
        let unitPrice = 0 //电价单价
        let unitService = 0 //服务费单价
        let kwh = 0 //可以充多少度电

        let pricing;

        const station = await this.chargingStation.findOne({
            where : {id : device.stationId},
            select : ['fastPricing', 'slowPricing']
        })

        if(device.chargeType == 1) {

        }else { //慢充
            if(station.slowPricing == 0) return null

            pricing = await this.device.slowPricing(station.slowPricing)
            if(!pricing) {
                return null
            }

            unitPrice = Number(pricing.price)
            unitService = Number(pricing.service)
        }

        

        let date = new Date()

        let order:KeyToValueType<ChargeOrder> = {
            //orderNo,
            payType : 2,
            //cardId : card.id,
            clientSource : 3,
            createTime : Math.floor(date.getTime() / 1000),
            unitPrice :  String(unitPrice),
            unitService : String(unitService),
            status : 0,
            deviceId,
            //userId,
            port : port,
            feeType : mode,
            precharge : chargeNumber,
            //chargeAmount : String(amount),
            feeAmount : '0',
            feeRewardAmount : '0'
        }

        if(device.owners != null && device.owners.length > 0) {
            order.merchantId = device.owners[0]
            const merchant = await this.chargOrder.queryMerchant(order.merchantId, ['profitRate'])
            order.profitRate = merchant.profitRate

            if(device.owners.length > 1) {
                order.subMerchantId = device.owners[1]
            }
        }

        if(device.stationId > 0) {
            order.stationId = device.stationId
        }

        

        const queryRunner = await this.chargOrder.createQueryRunner()
        await queryRunner.connect();
        await queryRunner.startTransaction();

        try {
            let orderId = 0
            order.payStatus = 1

            let card = await queryRunner.manager.findOne<ChargingCard>(ChargingCard, {
                where : {cardNo : String(cardNo)},
                select : ['amount', 'rewardAmount', 'id'],
                lock : {
                    mode : 'pessimistic_read'
                }
            })

            if(card == null || card.status != 1) {
                this.aliIot.pubToDevice(deviceId, device.aliProduct, resMsg)
                await queryRunner.rollbackTransaction();
                return -1
            }

            let rechargeBalance = Number(card.amount) //充值的余额
            let rewardBalance = Number(card.rewardAmount) //赠送的余额
            amount = Number((rechargeBalance + rewardBalance).toFixed(2))
            if(amount < 0.01) {
                this.aliIot.pubToDevice(deviceId, device.aliProduct, resMsg)
                await queryRunner.rollbackTransaction();
                return -2
            }

            kwh = Number((amount / (unitPrice + unitService)).toFixed(2)) //可以充多少度电
            
            if(kwh < 0.01) {
                this.aliIot.pubToDevice(deviceId, device.aliProduct, resMsg)
                return
            }

            if(mode == 0) { //时间
                chargeNumber = kwh / pricing.hourKwh
            }else if(mode == 1){
                chargeNumber = kwh
            }

            order.orderNo = this.chargOrder.generateOrderNo(card.userId)
            order.chargeAmount = String(amount)

            order.cardId = card.id
            order.userId = card.userId
            order.feeAmount = card.amount
            order.feeRewardAmount = card.rewardAmount


            const cardUpdate:{[key in keyof ChargingCard]? : ChargingCard[key]} = {}
            const log:{[key in keyof ChargingCardLog]? : ChargingCardLog[key]} = {
                cardId : card.id,
                userId : card.userId,
                amount : card.amount,
                rewardAmount : card.rewardAmount,
                beforeAmount : card.amount,
                beforeRewardAmount : card.rewardAmount,
                afterAmount : '0',
                afterRewardAmount : '0',
                type : 2
            }

            if(rechargeBalance > 0) {
                cardUpdate.amount = '0'
            }
            if(rewardBalance > 0) {
                cardUpdate.rewardAmount = '0'
            }


            //更新卡的余额
            const r1 = await queryRunner.manager.update<ChargingCard>(ChargingCard, { id : card.id }, {})
            if(r1.affected < 1) {
                await queryRunner.rollbackTransaction();
                return -2
            }

            

            //加入订单
            const r2 = await queryRunner.manager.insert<ChargeOrder>(ChargeOrder, order)
            if(!r2.raw || r2.raw.affectedRows < 1) {
                await queryRunner.rollbackTransaction();
                return -3
            }
            log.relationId = orderId = r2.raw.insertId


            //加入钱包明细
            log.createTime = order.createTime
            const { raw } = await queryRunner.manager.insert<ChargingCardLog>(ChargingCardLog, log)
            if(!raw || raw.affectedRows < 1) {
                await queryRunner.rollbackTransaction();
                return -4
            }

            resMsg.order = String(orderId).padStart(16, '0')
            resMsg.msgId = resMsg.order.padStart(32, '0')
            resMsg.s = 1

            const res = await this.aliIot.pubToDevice(deviceId, device.aliProduct, resMsg)
            if(!res || !res.body || !res.body.success) {
                await queryRunner.rollbackTransaction();
                return -5
            }


            await queryRunner.commitTransaction();

            return orderId

        } catch (error) {
            await queryRunner.rollbackTransaction();
            return null
        } finally {
            this.cache.del(lockKey)
            await queryRunner.release();
        }
        
    }



    pubCharge(orderId:number|string, deviceId:number, port:number, mode:number, time:number, kwh:number, aliProduct:string) {
        const orderNo = String(orderId)
        const msg:any = {
            type : 1,
            port,
            order : orderNo.padStart(16, '0'),
            m : mode,
            msgId : orderNo.padStart(32, '0'),
            t : time,
            q : kwh
        }

        return this.aliIot.pubToDevice(deviceId, aliProduct, msg) 
    }

    stop(orderId:number|string, deviceId:number, port:number, aliProduct:string) {
        const orderNo = String(orderId)
        const msg:any = {
            type : 5,
            port,
            order : orderNo.padStart(16, '0'),
            msgId : orderNo.padStart(32, '0'),
        }


        return this.aliIot.pubToDevice(deviceId, aliProduct, msg)
    }

    walletUpdate(amount:number, rechargeBalance:number, rewardBalance:number, order, wallet:User) {
        let walletUpdate:{amount? : string, rewardAmount? : string} = {}
        let walletLog:{[key in keyof UserWalletLog]? : UserWalletLog[key]} = {
            userId : order.userId,
            beforeAmount : wallet.amount,
            beforeRewardAmount : wallet.rewardAmount
        }

        if(rechargeBalance >= amount) { //充值余额够钱
            order.feeAmount = order.chargeAmount //充电订单中充值余额支出部分
            walletUpdate.amount = String(rechargeBalance - amount) //钱包充值余额，更新钱包
            

            walletLog.amount = String(-amount) //余额部分支付的
            //支出之后钱包的余额记录
            walletLog.afterAmount = walletUpdate.amount
            walletLog.afterRewardAmount = wallet.rewardAmount //赠送余额不变
        }else {
            let offset = amount - rechargeBalance
            
            order.feeRewardAmount = String(offset) //充电订单中赠送余额支出部分
            walletUpdate.rewardAmount = String(rewardBalance - offset) //钱包赠送余额，更新钱包
            
            walletLog.rewardAmount = String(-offset)  //赠送余额支出部分

            //支出之后钱包的赠送余额记录
            walletLog.afterRewardAmount = walletUpdate.rewardAmount

            if(rechargeBalance > 0) { //如果充值余额有钱的
                order.feeAmount = String(rechargeBalance) //充值余额全部给出
                walletUpdate.amount = '0' //钱包充值余额，更新钱包

                walletLog.amount = String(-rechargeBalance)
                walletLog.afterAmount = walletUpdate.amount
            }

        }

        return {
            walletLog,
            walletUpdate
        }
    }



    async privateCharge(userId:number, device:Device, deviceId:number, port:number, mode:number, chargeNumber:number) {
        let kwh = 0 //充电度数
        let times = 0 //充电时间

        if(mode == 0) { //时间
            kwh = chargeNumber * 2
            times = chargeNumber * 60
            
        }else if(mode == 1){
            kwh = chargeNumber
        }
        
        const r = await this.privateChargeRecord.insert({
            userId,
            deviceId,
            port,
            feeType : mode,
            precharge : chargeNumber,
            status : 0,
            createTime : Math.floor(Date.now() / 1000)
        })

        if(!r.raw || r.raw.affectedRows < 1) {
            return false
        }
        const id = r.raw.insertId
        const res = await this.pubCharge(id + 'P', deviceId, port, mode, times, kwh, device.aliProduct)
        if(!res || !res.body || !res.body.success) {
            await this.privateChargeRecord.update({id : r.raw.insertId}, {status : 3});
            return false
        }

        this.cache.set(this.PstartChargeResKey + id, 'w', 20)

        return id
    }
}
